package bsts.controller;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import bsts.model.*;
import bsts.exception.*;
//Author - Than Htet Aung - A0079950R : 21-March-2011
public class BillController {		
	public static final double GST_RATE = 0.07; //7 perscent GST
	public static final String BILL_DATE_FORMAT = "yyyyMM";
	public static final String BILL_STR_DATE_FORMAT = "dd-MMM-yyyy";
	private Date ChargesStartDate;
	private Date ChargesEndDate;
	private Date BillDueDate;
	private SimpleDateFormat billDateFormat;
	private SimpleDateFormat DateFormatInBillString;
	private ArrayList<Bill> billHistory;
	private ArrayList<Payment> paymentHistory;	
	private IOController ioController;	
	private CustomerController customerController;
	/*
	//For testing
	public ArrayList<Bill> billHistory;
	public ArrayList<Payment> paymentHistory;
	public BillController(){
		billDateFormat = new SimpleDateFormat(BILL_DATE_FORMAT);
		DateFormatInBillString = new SimpleDateFormat(BILL_STR_DATE_FORMAT);
		ioController = new IOController();
		//this.billHistory = ioController.readBills();
		//this.paymentHistory = ioController.readPayments();
		this.billHistory = new ArrayList<Bill>();
		this.paymentHistory = new ArrayList<Payment>();
		
		ChargesStartDate  = this.getChargesStartDate();
		ChargesEndDate = this.getChargesEndDate();
		BillDueDate = this.getBillDueDate();
	}
	*/
	
	public BillController(){
		billDateFormat = new SimpleDateFormat(BILL_DATE_FORMAT);
		DateFormatInBillString = new SimpleDateFormat(BILL_STR_DATE_FORMAT);
		ioController = new IOController();
		customerController = new CustomerController();
		this.billHistory = ioController.RetrieveBills();
		this.paymentHistory = ioController.RetrievePayments();		
		ChargesStartDate  = this.getChargesStartDate();
		ChargesEndDate = this.getChargesEndDate();
		BillDueDate = this.getBillDueDate();
	}
	
	/*Create monthly bill
	//Account number
	//1. Get account 
	//2. Get subscription detail
	//3. Get usage charges from transaction
	//4. Get payment history
	//5. Manipulate data
	//Finish defining the process 22/March/2011
	 * Assume that billing day for every month is 22 , so monthly bill can be generated only on or after 22nd of the month	 
	 */
	public void createMonthlyBill(Account account,User billUser)
	throws BadGenerateBillException{	
		if(!validAccountToBill(account)) throw new BadGenerateBillException("Account is not activated");						
		double previousBalance = 0.0;
		double previousPayment = 0.0;
		double currentBalance = 0.0;
		StringBuffer billString = new StringBuffer("One# \n ");		
		StringBuffer SummaryOfCharges = new StringBuffer("Summary Current Charges \n");
		SummaryOfCharges.append(generateRepeatedChars(35," ")+"Amount(S$)"+generateRepeatedChars(4,".")+"Total(S$)\n");
		StringBuffer ChargesDetail = new StringBuffer("\n---------------------------------------- Account Details -----------------------------------------\n");
		ChargesDetail.append(generateRepeatedChars(35,".")+"Amount(S$)"+generateRepeatedChars(4,".")+"Total(S$)\n");
		billString.append(getFormattedRightSideString("One# Pte Ltd \n"));
		billString.append(getFormattedRightSideString("41 Uni Lane \n"));
		billString.append(getFormattedRightSideString("#02-003, \n"));
		billString.append(getFormattedRightSideString("Singapre 654092 \n"));
		billString.append(getFormattedRightSideString("Call 1654 \n"));
		billString.append(getFormattedRightSideString("Email:customer@onehash.com \n\n"));
		billString.append(customerController.getCustomerDetail(account.getAccountNo()).getCustomerStringForBill()+"\n\n");	
		//Validate the account		
		//Check that is it the billing period?
		int day = Calendar.getInstance().get(Calendar.DATE);		
		if(day > 21){					
			Bill lastBill = getLastBill(account.getAccountNo());
			double finalPay = 0.0;
			double totalCurrentBalance = 0.0;
			if(lastBill!= null){
				System.out.println("Last bill is not null");
				if(IsThisMonthBill(lastBill,account.getAccountNo())) throw new BadGenerateBillException("For this month bill is ready already");
			}
			TransactionController transactionController = new TransactionController();			
			if(lastBill != null){				
				previousBalance = lastBill.getBalance();
				System.out.println("Last month bill is not null and the balance is "+previousBalance);
				previousPayment = getPaymentOnBill(lastBill.getBillNo());
				System.out.println("Last month bill is not null and the payment is "+previousPayment);
			}			
			//Assume that there is no pro-rated charges			
			for(Subscription subscription:account.getSubscriptionCollection()){
				//Check if it is able to bill?
				if(validSubscriptionToBill(subscription))				   				  
				{				   
					/*plan.getMonthlySubscription(); 
					 *identify the plan type (Digital voice , cable tv, mobile)
					 *get the call transaction and calculate the usage charges					     				     			  
					 */
					Object o = subscription.getPlan();
					double TotalSubscriptionCharges = 0.0;
					double TotalUsageCharges = 0.0;
					StringBuffer SubscriptionChargesDetail = new StringBuffer();
					StringBuffer UsageChargesDetail = new StringBuffer();
					if(o instanceof MobileVoice){
						currentBalance = manageMobileVoicePlan(currentBalance,
								SummaryOfCharges, ChargesDetail,
								transactionController, subscription, o,
								TotalSubscriptionCharges, TotalUsageCharges,
								SubscriptionChargesDetail, UsageChargesDetail);
					}else if (o instanceof DigitalVoice){
						currentBalance = manageDigitalVoicePlan(currentBalance,
								SummaryOfCharges, ChargesDetail,
								transactionController, subscription, o,
								TotalSubscriptionCharges, TotalUsageCharges,
								SubscriptionChargesDetail, UsageChargesDetail);
					}else if(o instanceof CableTV){
						currentBalance = manageCableTVPlan(currentBalance,
								SummaryOfCharges, ChargesDetail, o,
								TotalSubscriptionCharges, SubscriptionChargesDetail);
					}				   
					SummaryOfCharges.append(generateRepeatedChars(50,".")+ currentBalance  +"\n\n");
					ChargesDetail.append(generateRepeatedChars(50,".")+currentBalance  +"\n\n\n");
				}
			}
			totalCurrentBalance = (currentBalance+roundTwoDecimals((currentBalance*GST_RATE)));
			totalCurrentBalance = roundTwoDecimals(totalCurrentBalance);
			finalPay = (previousBalance+totalCurrentBalance-previousPayment);
			finalPay = roundTwoDecimals(finalPay);
			billString.append("Account No: "+account.getAccountNo()+"\n");
			billString.append("Bill date: "+DateFormatInBillString.format(Calendar.getInstance().getTime())+"\n");
			billString.append("Due date :"+DateFormatInBillString.format(this.BillDueDate)+"\n");
			billString.append("\n\nAt a Glance \n");
			billString.append("\nPrevious Balance ................................$"+previousBalance+" \n");
			billString.append("Payments.........................................$"+previousPayment+" \n");
			billString.append("\nCurrent Charges Due on "+DateFormatInBillString.format(this.BillDueDate)+"...............$"+totalCurrentBalance+" \n\n");			
			billString.append("Please pay.......................................$"+finalPay+" \n");
			billString.append("\n\nOne# \n");
			billString.append("Account No: "+account.getAccountNo()+"\n");
			billString.append("Bill date: "+DateFormatInBillString.format(Calendar.getInstance().getTime())+"\n");
			String GSTChargesStr = "\n"+ getFormattedAmountString("Total GST")+generateRepeatedChars(15,".")+roundTwoDecimals((currentBalance*GST_RATE));			
			String TotalChargesStr = "\n"+ getFormattedAmountString("Total Current Charges")+generateRepeatedChars(15,".")+totalCurrentBalance;
			billString.append(SummaryOfCharges +GSTChargesStr + TotalChargesStr + ChargesDetail+"\n"+ GSTChargesStr + TotalChargesStr );
			//System.out.println(billString.toString());
			//[accountno+_bill+year+month in two digit]
			System.out.println(" Before adding bill, the bill size = "+billHistory.size());
			addBill(account.getAccountNo(), billUser, account, finalPay);
			System.out.println(" After adding bill, the bill size = "+billHistory.size());
			boolean updateBillStatus = ioController.UpdateBills(billHistory);
			boolean writeBillStatus = ioController.WriteBill(account.getAccountNo(), billString.toString(),"bills_"+billDateFormat.format(Calendar.getInstance().getTime()));
			System.out.println("Update bill status = " + updateBillStatus);
			System.out.println("Write bill status = " + writeBillStatus);
		}else{
			throw new BadGenerateBillException("Bill for this month will be available after 22d of this month.");
		}			
	}

	private double manageCableTVPlan(double currentBalance,
			StringBuffer SummaryOfCharges, StringBuffer ChargesDetail,
			Object o, double TotalSubscriptionCharges,
			StringBuffer SubscriptionChargesDetail) {
		double TotalUsageCharges;		
		//assume that additional line will be additional feature
		int AdditionalChannel = 0;		
		double AdditionalChannelCharges = 0.0;
		SubscriptionChargesDetail.append(((CableTV)o).getName() +"\n");
		SummaryOfCharges.append(((CableTV)o).getName() + "\n");
		for(Feature f: ((CableTV)o).getFeatures()){						  
			if(f.getRateType()== RateType.PerMonth && validFeatureToBill(f))
			{				
				double amount = CalculateMonthlySubscriptionCharge(f);
				amount = roundTwoDecimals(amount);
				currentBalance += amount;
				TotalSubscriptionCharges += amount;
			}
			else if(f.getRateType() == RateType.PerChannel && f.getSubscriptionStatus()!=SubscriptionStatus.Terminated){							 
				AdditionalChannelCharges = f.getRate();
				AdditionalChannel ++;
			}						 
		}
		TotalUsageCharges = AdditionalChannelCharges * AdditionalChannel;
		TotalUsageCharges = roundTwoDecimals(TotalUsageCharges);
		currentBalance += TotalUsageCharges;
		ChargesDetail.append(SubscriptionChargesDetail.toString());
		ChargesDetail.append(getFormattedAmountString("Subscription charges")+TotalSubscriptionCharges +"\n");					   
		ChargesDetail.append(getFormattedAmountString("Add. Channel charges ($"+AdditionalChannelCharges+"x"+AdditionalChannel+")")+TotalUsageCharges +"\n");
		SubscriptionChargesDetail = new StringBuffer();		  
		SummaryOfCharges.append(getFormattedAmountString("Subscription charges")+TotalSubscriptionCharges +"\n");
		SummaryOfCharges.append(getFormattedAmountString("Add. Channel charges")+TotalUsageCharges +"\n");
		return currentBalance;
	}

	private double manageDigitalVoicePlan(double currentBalance,
			StringBuffer SummaryOfCharges, StringBuffer ChargesDetail,
			TransactionController transactionController,
			Subscription subscription, Object o,
			double TotalSubscriptionCharges, double TotalUsageCharges,
			StringBuffer SubscriptionChargesDetail,
			StringBuffer UsageChargesDetail) {
		UsageChargesDetail.append("\nUsage Charges\n");
		SubscriptionChargesDetail.append(((DigitalVoice)o).getName() +" "+((DigitalVoice)o).getPhoneNo()+"\n");
		SummaryOfCharges.append(((DigitalVoice)o).getName() + "\n");
		for(Feature f: ((DigitalVoice)o).getFeatures()){						  
			if(f.getRateType()== RateType.PerMonth && validFeatureToBill(f))
			{
				double amount = CalculateMonthlySubscriptionCharge(f);
				amount = roundTwoDecimals(amount);
				TotalSubscriptionCharges += amount;						   
				SubscriptionChargesDetail.append(getFormattedAmountString(f.getName())+amount+"\n");
				currentBalance +=amount;
			}
			else if(f.getRateType() == RateType.PerSecond && f.getSubscriptionStatus()!=SubscriptionStatus.Terminated){
				ArrayList<Transaction> transactions = transactionController.getTransactions(subscription.getSubscriptionId(), f.getFeatureId(), ChargesStartDate,ChargesEndDate);
				long TotalCallDuration = transactionController.getTotalCallDuration(transactions);
				double TotalCharges = TotalCallDuration * f.getRate();
				TotalCharges = roundTwoDecimals(TotalCharges);
				UsageChargesDetail.append(getFormattedAmountString(f.getName())+TotalCharges+"\n");
				currentBalance += TotalCharges;
				TotalUsageCharges +=TotalCharges; 
			}						 
		}
		ChargesDetail.append(SubscriptionChargesDetail.toString()+UsageChargesDetail.toString()+"\n");
		SubscriptionChargesDetail = new StringBuffer();
		UsageChargesDetail = new StringBuffer();		
		SummaryOfCharges.append(getFormattedAmountString("Subscription charges")+TotalSubscriptionCharges +"\n");
		SummaryOfCharges.append(getFormattedAmountString("Usage charges")+TotalUsageCharges +"\n");
		return currentBalance;
	}

	private double manageMobileVoicePlan(double currentBalance,
			StringBuffer SummaryOfCharges, StringBuffer ChargesDetail,
			TransactionController transactionController,
			Subscription subscription, Object o,
			double TotalSubscriptionCharges, double TotalUsageCharges,
			StringBuffer SubscriptionChargesDetail,
			StringBuffer UsageChargesDetail) {
		UsageChargesDetail.append("\nUsage Charges\n");
		SubscriptionChargesDetail.append(((MobileVoice)o).getName() +" "+((MobileVoice)o).getPhoneNo()+"\n");
		SummaryOfCharges.append(((MobileVoice)o).getName() + "\n");
		for(Feature f: ((MobileVoice)o).getFeatures()){						  
			if(f.getRateType()== RateType.PerMonth && validFeatureToBill(f))
			{
				double amount = CalculateMonthlySubscriptionCharge(f);
				amount = roundTwoDecimals(amount);
				TotalSubscriptionCharges +=amount;						   
				SubscriptionChargesDetail.append(getFormattedAmountString(f.getName())+amount+"\n");
				currentBalance += amount;				
			}
			else if(f.getRateType() == RateType.PerSecond && f.getSubscriptionStatus()!=SubscriptionStatus.Terminated){
				ArrayList<Transaction> transactions = transactionController.getTransactions(subscription.getSubscriptionId(), f.getFeatureId(), ChargesStartDate,ChargesEndDate);
				long TotalCallDuration = transactionController.getTotalCallDuration(transactions);
				double TotalCharges = TotalCallDuration * f.getRate();
				TotalCharges = roundTwoDecimals(TotalCharges);
				UsageChargesDetail.append(getFormattedAmountString(f.getName())+TotalCharges+"\n");
				currentBalance += TotalCharges;
				TotalUsageCharges +=TotalCharges; 
			}						 
		}
		ChargesDetail.append(SubscriptionChargesDetail.toString()+UsageChargesDetail.toString()+"\n");
		SubscriptionChargesDetail = new StringBuffer();
		UsageChargesDetail = new StringBuffer();
		SummaryOfCharges.append(getFormattedAmountString("Subscription charges")+TotalSubscriptionCharges +"\n");
		SummaryOfCharges.append(getFormattedAmountString("Usage charges")+TotalUsageCharges +"\n");
		return currentBalance;
	}

	/*
	 * Add monthly bill
	 */
	private void addBill(String accountNo, User billUser, Account account,
			double balance) {
		Bill currentMonthBill = new Bill(accountNo+"_bills_"+billDateFormat.format(Calendar.getInstance().getTime()),
				Calendar.getInstance().getTime(), 
				this.getBillDueDate(), billUser,balance, account);
		System.out.println("Bill no = "+accountNo+"_bills_"+billDateFormat.format(Calendar.getInstance().getTime()));
		billHistory.add(currentMonthBill);
	}
	
	//Get Monthly bill based on the bill object, simply retrieve the bill file string
	public String getMonthlyBill(String accountNo,Bill bill){
		return ioController.ReadBill(accountNo, "bills_"+ billDateFormat.format(bill.getBillDate()));
	}
	
	//Retrieve list of bill based on account
	public ArrayList<Bill> getBills(Account account){
		System.out.println("In Get Bills method \n Number of bill = "+this.billHistory.size());
		ArrayList<Bill> bills = new ArrayList<Bill>();
		for(Bill b:this.billHistory){
			if(b.getAccount().equals(account)){
				bills.add(b);
			}
		}
		Collections.sort(bills);
		return bills;
	}
	/*
	 * Retrieve bill
	 */
	public Bill getBill(String billNo){
		for(Bill b : this.billHistory){
			if(b.getBillNo().equals(billNo)){
				return b;
			}
		}
		return null;
	}

	/*
	 * Retrieve the last month bill
	 */
	public Bill getLastBill(String accountNo){
		//Try to get current month bill
		Calendar cal= Calendar.getInstance();
		String previousBillNo = accountNo+"_bills_"+billDateFormat.format(cal.getTime());
		Bill resultBill = getBill(previousBillNo);
		if(resultBill==null){
			//Check the previous month bill
			cal.add(Calendar.MONTH, -1);
			previousBillNo = accountNo+"_bills_"+billDateFormat.format(cal.getTime());
			resultBill = getBill(previousBillNo);
			return resultBill;
		}else{				
			return resultBill;
		}
	}

	//Get total payment amount on the bill
	public double getPaymentOnBill(String billNo){
		double paymentAmount = 0.0;
		for(Payment p: paymentHistory){
			if(p.getReferenceBillNo().equals(billNo)){
				paymentAmount += p.getPaymentReceivedAmt();
			}
		}
		return paymentAmount;
	}

	//get the charges end date or billing cycle end date
	public Date getChargesEndDate(){		
		int month = Calendar.getInstance().get(Calendar.MONTH);
		int year = Calendar.getInstance().get(Calendar.YEAR);

		Calendar cal = Calendar.getInstance();
		cal.clear();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, month);
		cal.set(Calendar.DATE, 22);

		return cal.getTime();
	}

	//Get the charges start date or billing cycle start date
	public Date getChargesStartDate(){		
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MONTH, -1);
		int month =cal.get(Calendar.MONTH);
		int year = cal.get(Calendar.YEAR);			
		cal.clear();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, month);
		cal.set(Calendar.DATE, 23);		
		return cal.getTime();
	}

	//get the bill due date
	public Date getBillDueDate(){		
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MONTH, 1);
		int month =cal.get(Calendar.MONTH);
		int year = cal.get(Calendar.YEAR);			
		cal.clear();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, month);
		cal.set(Calendar.DATE, 15);		
		return cal.getTime();
	}
	
	//get the formatted amount - generate number of space which will require
	public String getFormattedAmountString(String description){		
		String ResultString = description;
		int requireSpace = 0;
		if(description.length() > 34){
			requireSpace = 35;
			ResultString = ResultString + "\n";
		}else{
			requireSpace = 35-description.length();			
		}
		String reqStr = generateRepeatedChars(requireSpace,".");
		ResultString = ResultString + reqStr;
		return ResultString;
	}
	//get the formatted amount - generate number of space which will require
	public String getFormattedRightSideString(String description){		
		String ResultString = description;
		int requireSpace = 35;		
		String reqStr = generateRepeatedChars(requireSpace,".");
		ResultString = reqStr+generateRepeatedChars(15,".")+ResultString;
		return ResultString;
	}
	//Generate repeated string
	private String generateRepeatedChars(int requireSpace,String preferStr) {
		StringBuffer spaceBuffer = new StringBuffer();
		for(int i=0;i<requireSpace;i++)
			spaceBuffer.append(preferStr);
		return spaceBuffer.toString();
	}
	//Check that the account is valid to bill
	public boolean validAccountToBill(Account account){
		if(account.getAccountStatus()== AccountStatus.Deactivated &&
				account.getDeactivatedDate().before(getChargesStartDate())
				){
			return false;
		}
		return true;
	}
	//Check that the subscription is valid to bill
	public boolean validSubscriptionToBill(Subscription subscription){
		if(subscription.getSubscriptionStatus() == SubscriptionStatus.Terminated &&
				subscription.getDateTerminated().before(getChargesStartDate())
				){
			return false;
		}
		return true;
	}
	//Check that the feature is valid to bill
	public boolean validFeatureToBill(Feature subscribedFeature){
		if(subscribedFeature.getSubscriptionStatus() == SubscriptionStatus.Terminated &&
				subscribedFeature.getDateTerminated().before(getChargesStartDate())
				){
			return false;
		}
		return true;
	}
	//Check that the feature is required to pro rated? and get the monthly subscription charges
	public double CalculateMonthlySubscriptionCharge(Feature subscribedFeature){
		//Check the monthly subscription need to be pro rated?
		if(subscribedFeature.getSubscriptionStatus() == SubscriptionStatus.Terminated &&
				subscribedFeature.getDateTerminated().after(getChargesStartDate())
				){
			try{
				long diffInMiliSec = subscribedFeature.getDateTerminated().getTime() - getChargesStartDate().getTime();
				double diffInDay = (diffInMiliSec/(1000*60*60*24));			
				return subscribedFeature.getRate() * (diffInDay/30);
			}catch(Exception eee){
				System.out.println(eee.getMessage());
				return subscribedFeature.getRate();
			}
		}else{
			return subscribedFeature.getRate();
		}
	}
	
	public boolean IsThisMonthBill(Bill bill,String accountNo){
		String thisMonthBillno = accountNo+"_bills_"+billDateFormat.format(Calendar.getInstance().getTime());
		if(bill.getBillNo().equals(thisMonthBillno)) return true;
		else return false;
	}
	
	double roundTwoDecimals(double d) {
    	DecimalFormat twoDForm = new DecimalFormat("#.##");
    	return Double.valueOf(twoDForm.format(d));
	}
}
